package net.monare.mifss.ws.rest;

import net.monare.mifss.configurations.pojo.ApiKey;
import net.monare.mifss.configurations.pojo.ApiMode;
import net.monare.mifss.configurations.pojo.ContentMetaDataDef;
import net.monare.mifss.services.ContentService;
import net.monare.mifss.services.StorageService;
import net.monare.mifss.ws.util.MifssWsConstants;
import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.UUID;

@Controller
public class MifssContentController extends BaseMifssController {

    protected static Logger logger = Logger.getLogger(MifssContentController.class);

    @Autowired
    private View jsonView;

    @Autowired
    private ContentService contentService;

    @Autowired
    private StorageService storageService;

    // private MultipartResolver multipartResolver;

    @RequestMapping(value = "/content/retrieve", method = RequestMethod.GET)
    public ModelAndView retrieveContentMetaData(

            @RequestHeader(value = MifssWsConstants.API_KEY_HEADER, required = true) String apiKeyHash,
            @RequestHeader(value = MifssWsConstants.CONTENT_UUID_HEADER, required = true) String contentUUID,
            @RequestHeader(value = MifssWsConstants.VERIFY_CONTENT_HEADER, defaultValue = "false") String verifyContentString,
            @RequestHeader(value = MifssWsConstants.STORAGE_NAME, required = true) String storageName) {

        ContentMetaDataDef cm;

        // retrieve API key
        try {
            storageService.verifyApiKey(storageName, apiKeyHash, ApiMode.READ);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return new ModelAndView(jsonView, MifssWsConstants.ERROR_FIELD, "Error occured while trying to verify api key:"
                    + e.getMessage());
        }

        // get file name for given UUID
        try {
            cm = contentService.retriveContentMetaData(contentUUID, storageName);
            if (cm == null) {
                return new ModelAndView(jsonView, MifssWsConstants.ERROR_FIELD, "No content for UUID:" + contentUUID);
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return new ModelAndView(jsonView, MifssWsConstants.ERROR_FIELD, "Error occured while trying to retrive content metadata. "
                    + e.getMessage());
        }

        // get content
        try {

            ModelAndView mav = new ModelAndView(jsonView);

            mav.addObject(MifssWsConstants.DATA_FIELD, cm);

            return mav;

        } catch (Exception e) {

            logger.error(e.getMessage(), e);
            return new ModelAndView(jsonView, MifssWsConstants.ERROR_FIELD, "Error occured while trying to retrive content. "
                    + e.getMessage());

        } finally {

        }

    }

    @RequestMapping(value = "/content/upload", method = RequestMethod.POST)
    public ModelAndView upload(@RequestPart(MifssWsConstants.FILE_UPLOAD) MultipartFile multipartFile,
                               @RequestHeader(value = MifssWsConstants.API_KEY_HEADER, required = true) String apiKeyHash,
                               @RequestHeader(value = MifssWsConstants.STORAGE_NAME, required = true) String storageName) {

        File tempFile = null;

        // retrieve API key
        try {
            // Validate API key for given storage, Error is thrown on invalid
            // api
            storageService.verifyApiKey(storageName, apiKeyHash, ApiMode.WRITE);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return new ModelAndView(jsonView, MifssWsConstants.ERROR_FIELD, "Error occured while trying to verify api key:"
                    + e.getMessage());
        }

        try {

            tempFile = createTmpFile(multipartFile.getOriginalFilename());
            multipartFile.transferTo(tempFile);
            ContentMetaDataDef cmd = contentService.writeContent(multipartFile.getOriginalFilename(), tempFile, storageName);

            multipartFile = null;

            return new ModelAndView(jsonView, MifssWsConstants.DATA_FIELD, cmd);

        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return new ModelAndView(jsonView, MifssWsConstants.ERROR_FIELD, "Error occured while trying to save content: " + e.getMessage());

        } finally {

            deleteFile(tempFile);
        }

    }

    private void deleteFile(File file) {
        try {

            Files.deleteIfExists(Paths.get(file.getAbsolutePath()));
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    @RequestMapping(value = "/content/download/api/{apiKey}/storage/{storageIdString}/uuid/{contentUUID}/inline/{inline}", method = RequestMethod.GET)
    public
    @ResponseBody
    HttpEntity<byte[]> download(HttpServletResponse response,

                                @PathVariable("apiKey") String apiKeyHash, @PathVariable("storageIdString") String storageName,
                                @PathVariable("contentUUID") String contentUUID, @PathVariable("inline") boolean inline
    ) {

        ContentMetaDataDef cm;
        String fileName;
        File outFile = null;

        // get file name for given UUID
        try {
            cm = contentService.retriveContentMetaData(contentUUID, storageName);
            if (cm == null) {
                return null;
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return null;
        }

        // retrieve API key
        try {
            storageService.verifyApiKey(storageName, apiKeyHash, ApiMode.READ);

        } catch (Exception e) {

            // TODO
            logger.error(e.getMessage(), e);
            return null;
        }

        // get content
        try {

            HttpHeaders httpHeaders = new HttpHeaders();

            fileName = cm.getFileName();

            if (fileName != null) {

                outFile = createTmpFile(fileName);

                contentService.retriveContent(outFile, contentUUID, storageName, false);

                InputStream inputStream = null;

                try {
                    inputStream = new FileInputStream(outFile);
                } catch (FileNotFoundException e) {

                    // Also useful, this is a good was to serve down an error
                    // message
                    String msg = "ERROR: Could not find the file specified.";
                    httpHeaders.setContentType(MediaType.TEXT_PLAIN);
                    return new ResponseEntity<byte[]>(msg.getBytes(), httpHeaders, HttpStatus.NOT_FOUND);

                }

                Long fileSize = outFile.length();
                response.setContentLength(fileSize.intValue());

                OutputStream outputStream = null;

                try {
                    outputStream = response.getOutputStream();
                } catch (IOException e) {
                    String msg = "ERROR: Could not generate output stream.";
                    httpHeaders.setContentType(MediaType.TEXT_PLAIN);
                    if (inputStream != null)
                        inputStream.close();
                    return new ResponseEntity<byte[]>(msg.getBytes(), httpHeaders, HttpStatus.NOT_FOUND);
                }
                byte[] buffer = new byte[1024];

                int read = 0;
                try {

                    // send headers
                    response.setContentType(cm.getMimeType());

                    // if download flag
                    if (inline) {
                        response.setHeader("Content-Disposition", "filename=" + cm.getFileName());
                    } else {
                        response.setHeader("Content-Disposition", "attachment; filename=" + cm.getFileName());
                    }

                    while ((read = inputStream.read(buffer)) != -1) {
                        outputStream.write(buffer, 0, read);
                    }
                    // close the streams to prevent memory leaks
                    outputStream.flush();
                    outputStream.close();
                    inputStream.close();

                } catch (Exception e) {
                    String msg = "ERROR: Could not read file.";
                    httpHeaders.setContentType(MediaType.TEXT_PLAIN);
                    return new ResponseEntity<byte[]>(msg.getBytes(), httpHeaders, HttpStatus.NOT_FOUND);
                }

            }

        } catch (Exception e) {

            logger.error(e.getMessage(), e);
            return null;

        } finally {

            // remove temporary folder
            FileUtils.deleteQuietly(outFile);

        }

        return null;

    }

    private File createTmpFile(String fileName) throws IOException {

        return File.createTempFile(UUID.randomUUID().toString() + "-", ".tmp");

    }

    // retrieve

    public void setJsonView(View view) {
        jsonView = view;
    }

}
